package com.sinog.core.utilbean;

/**
 * @desc ID生成器
 * @author zy
 * @date 2019年7月2日 09点20分
 */
public final class IdGen {

    private final long workerId;
    private final long datacenterId;
    private long sequence;
    /**
     * 1993-03-03
     */
    private static final long TWEPOCH = 731088000L;
    /**
     * 节点ID长度
     */
    private static final long WORKER_ID_BITS = 3L;
    /**
     * 数据中心ID长度
     */
    private static final long DATACENTER_ID_BITS = 3L;
    /**
     * 最大支持机器节点数0~31，一共32个
     */
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    /**
     * 最大支持数据中心节点数0~31，一共32个
     */
    private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);
    /**
     * 序列号8位
     */
    private static final long SEQUENCE_BITS = 8L;
    /**
     * 机器节点左移8位
     */
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
    /**
     * 数据中心节点左移17位
     */
    private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    /**
     * 时间毫秒数左移22位
     */
    private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;
    /**
     * 32
     */
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
    private long lastTimestamp = -1L;

    private static class IdGenHolder {
        private static final IdGen INSTANCE = new IdGen();
    }

    public static IdGen get() {
        return IdGenHolder.INSTANCE;
    }

    private IdGen() {
        this(0L, 0L);
    }

    private IdGen(long workerId, long datacenterId) {
        if(workerId > MAX_WORKER_ID || 0 > workerId) {
            throw new IllegalArgumentException(String
                    .format("worker Id can't be greater than %d or less than 0",MAX_WORKER_ID));
        }
        if(datacenterId > MAX_DATACENTER_ID || 0 > datacenterId) {
            throw new IllegalArgumentException(String
                    .format("datacenter Id can't be greater than %d or less than 0",MAX_DATACENTER_ID));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    /**
     * 描述
     * @return long
     */
    public synchronized long next() {
        // 获取当前毫秒数
        long timestamp = timeGen();
        // 如果服务器时间有问题(时钟后退) 报错。
        if(timestamp < lastTimestamp) {
            throw new RuntimeException(String
                    .format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        // 如果上次生成时间和当前时间相同,在同一毫秒内
        if(lastTimestamp == timestamp) {
            // sequence自增，因为sequence只有8bit，所以和sequenceMask相与一下，去掉高位
            sequence = (sequence + 1) & SEQUENCE_MASK;
            // 判断是否溢出,也就是每100毫秒内超过32，当为32时，与sequenceMask相与，sequence就等于0
            if(0 == sequence) {
                // 自旋等待到下一毫秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 如果和上次生成时间不同,重置sequence，就是下一毫秒开始，sequence计数重新从0开始累加
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        // 最后按照规则拼出ID。
        // 000000000000000000000000000000000000000000 00000 00000 000000000000
        // time datacenterId workerId sequence
        return ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | (datacenterId << DATACENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence;
    }

    /**
     * 描述
     * @param lastTimestamp lastTimestamp
     * @return long
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while(timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 司法部规定主键ID 为15位,使用时间随机数控制产生序列位数。100毫秒产生32个随机数。
     * @return long
     */
    private long timeGen() {
        return System.currentTimeMillis() / 100;
    }

    /**
     * 产生字符串形式的唯一ID
     * @return String
     */
    public static String strid() {
        return String.valueOf(get().next());
    }
}